<template>
    <div class="categoryTable">
        <div class="">
            <el-divider class="topLine"><i class="lineIcon el-icon-document-copy"></i><span
                    class="lineTitle">模块-总分类表列表</span></el-divider>
        </div>

        <div class="switchDrag">
            <!--绑定switchDraggable参数，默认false，点击拖拽，显示批量保存-->
            <el-switch class="switchDragChoose"
                       v-model="switchDraggable"
                       active-text="开启拖拽"
                       inactive-text="关闭拖拽">
            </el-switch>
            <el-button v-if="switchDraggable" type="primary" class="switchDragBtn" @click="batchDraggableSave">批量保存
            </el-button>
            <el-button type="primary" class="switchDragBtn" @click="addCat">新增分类</el-button>
            <el-button type="danger" class="switchDragBtn" @click="batchDel">批量删除</el-button>
        </div>
        <div class="categoryTree">

            <!--绑定data中的categorys
            :props绑定传入的参数及名称
            :expand-on-click="false"取消点击行收缩/展开子分类，只有点击箭头时展开
            @node-click:节点点击事件
            show-checkbox：显示选择框
            node-key：做整个树状结构的唯一标识，对应后端sql的uniId
            default-expand-all：默认展开全部节点
            default-expand-keys：默认展开的节点的数组
            draggable：开启拖拽
            :allow-drop:判断目标节点能否被放置 Function(draggingNode, dropNode, type)
            @node-drop：拖拽成功时，触发的事件。被拖拽节点对应的 Node、结束拖拽时最后进入的节点、被拖拽节点的放置位置（before、after、inner）、event
            |- type='prev'、'inner' 和 'next'，分别表示放置在目标节点前、插入至目标节点和放置在目标节点后

            ref=""为tree起别名，用于批量删除
            |-(leafOnly, includeHalfChecked) 接收两个 boolean 类型的参数
                1. 是否只是叶子节点，默认值为 false
                2. 是否包含半选节点，默认值为 false
                注意：传入一个数组

            --》设置4级分类：3级分类显示append，4级分类显示remove
            |-add判定：绑定level属性，sql中默认属性
            |-remove判定
              A：可以使用绑定的node属性，获取子节点信息的属性名=childNodes --》 v-if="node.level<=3"
                 node中的parent包含父节点信息data：node-》parent-》data-》list[0]-》uniId，用于展开之前操作元素的父级菜单
              B：可以使用绑定的data属性，获取子节点信息的属性名=children   --》 v-if="node.childNodes.length===0"

            -->

            <el-tree
                    :data="categorys"
                    :props="defaultProps"
                    :expand-on-click-node="false"
                    node-key="uniId"
                    show-checkbox
                    :default-expanded-keys="expendedKeys"
                    :draggable="switchDraggable"
                    :allow-drop="allowDrop"
                    @node-drop="handleDrop"
                    ref="categoryTree">
                <span class="tree-node" slot-scope="{node,data}">
                    <span class="nodeIcon glyphicon glyphicon-folder-open"></span>
                    <span class="labelName">{{node.label}}</span>
                    <span class="option-btn">
                    <el-button class="opt-btn-add" icon="el-icon-circle-plus" v-if="node.level<=3" type="primary"
                               size="mini" @click="() => append(data)" plain>新增</el-button>
                    <el-button class="opt-btn-edit" icon="el-icon-question"
                               type="primary"
                               size="mini" @click="() => edit(data)" plain>修改</el-button>
                    <el-button class="opt-btn-del" icon="el-icon-remove" v-if="node.childNodes.length===0"
                               type="primary" size="mini" @click="() => del(node,data)" plain>刪除</el-button>
                    </span>
                </span>

            </el-tree>
        </div>
        <!--
        :visible.sync ：接收一个boolean布尔值，true时显示
        -->
        <div class="dialogModal">
            <el-dialog
                    :title="dialogTitle"
                    :visible.sync="dialogVisible"
                    width="30%"
                    :close-on-click-modal="false"
            >
                <el-form :model="category" ref="category" :rules="rules">
                    <el-form-item label="分类名称">
                        <el-input v-model="category.name" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="排序">
                        <el-input v-model="category.sort" autocomplete="off"></el-input>
                    </el-form-item>
                    <el-form-item label="计量单位">
                        <el-input v-model="category.productUnit" autocomplete="off"></el-input>
                    </el-form-item>
                </el-form>

                <span slot="footer" class="dialog-footer">
                    <el-button @click="dialogVisible = false">取 消</el-button>
                    <el-button type="primary" @click="save('category')">确 定</el-button>
                </span>
            </el-dialog>

        </div>

    </div>
</template>

<script>

    export default {
        name: "CategorySet",
        //引用组件
        data() {
            let nameValidate = (rule, value, callback) => {
                function getLength(str) {
                    //如果为中文，替换为ccc，1个中文字符计算长度=1
                    return str.replace(/[^x00-xff]/g, 'c').length;
                }

                if (value === '') {
                    callback(new Error("分类名称不能为空"));
                } else if (getLength(value) < 2) {
                    callback(new Error("分类名称长度至少3位"));
                } else {
                    callback();
                }
            };
            return {
                message: '这里是大模块-总分类表内容页',
                categorys: [],
                expendedKeys: [], //tree默认展开的数组
                dialogVisible: false, //对话框显示，默认为false
                defaultProps: {
                    children: 'children', //子菜单的属性名
                    label: 'name' //显示的名称
                },
                category: {},
                rules: {
                    name: [
                        {validator: nameValidate, trigger: 'blur'}
                    ],
                },
                dialogType: "", //对话框类型：添加add/更新edit
                dialogTitle: "",
                maxLevel: 0, //拖拽节点的最大深度
                updateNodes: [], //存放所有拖拽后的节点位置/排序/id等
                switchDraggable: false, //绑定是否开启拖拽
                pId: [], //全局pId，批量保存为数组,
                uniIds:[], //批量删除
            }
        },
        mounted() {
            this.treeList();
        },
        methods: {
            //多选的方法
            toggleSelection(rows) {
                if (rows) {
                    rows.forEach(row => {
                        this.$refs.multipleTable.toggleRowSelection(row);
                    });
                } else {
                    this.$refs.multipleTable.clearSelection();
                }
            },
            handleSelectionChange(val) {
                this.multipleSelection = val;
            },
            //树形结构获取
            treeList() {
                Loadings.show();
                this.$axios.get(process.env.VUE_APP_SERVER + '/business/admin/category/treeList')
                    .then((response) => { //{解构响应数据}
                        Loadings.hide();
                        let resp = response.data;
                        this.categorys = resp.responseData;
                    })
            },
            append(data) {
                this.dialogType = "新增分类";
                this.dialogTitle = "新增模块分类";
                this.category = {};
                this.dialogVisible = true;
                this.category.parentId = data.uniId;
                //这里获取到的level是data中，而这里因为点击了父节点新增子节点，如果父节点为2，那么子节点+1=3
                this.category.level = data.level * 1 + 1;
                this.category.isShow = 1;
                /**对象解构：
                 * var {uniId,name,...}=this.category
                 * 发送时，直接发送解构对象的数据{uniId...}
                 */
            },
            addCat() {
                this.dialogType="新增一级分类";
                this.dialogTitle="新增主模块分类";
                this.category={};
                this.dialogVisible=true;
                this.category.parentId="00000000";
                this.category.level=1;
                this.category.isShow=1;

            },
            //1.list查询
            list() {
                Loadings.show();
                this.$axios.get(process.env.VUE_APP_SERVER + '/business/admin/category/dtoList')
                    .then((response) => {
                        Loadings.hide();
                        let resp = response.data;
                        this.categorys = resp.responseData;

                        //先清空level1数据
                        this.level1 = [];
                        //将所有数据格式化tree型结构
                        for (let i = 0; i < this.categorys.length; i++) {
                            //获取每一个cat结果[i]
                            let cats = this.categorys[i];
                            if (cats.parentId === "00000000") {
                                //level1放入cats，到level1集合
                                this.level1.push(cats);
                                //二级分类查询
                                for (let j = 0; j < this.categorys.length; j++) {
                                    let catsChild = this.categorys[j];
                                    //判断parentId=uniId
                                    if (catsChild.parentId === cats.uniId) {
                                        //children为空时，初始化一个children新数组
                                        if (Tool.isEmpty(cats.children)) {
                                            cats.children = [];
                                        }
                                        cats.children.push(catsChild);
                                    }
                                }
                            }

                        }
                    })
            },
            save(formName) {
                let responseMsg = '';
                //前端校验
                this.$refs[formName].validate((valid) => {
                    if (valid) {

                        this.$axios.post(process.env.VUE_APP_SERVER + '/business/admin/category/save', this.category)
                            .then((response) => {
                                let resp = response.data;
                                responseMsg = response;
                                if (resp.success) {
                                    //关闭对话框
                                    this.dialogVisible = false;
                                    this.treeList();
                                    //展开父级菜单
                                    this.expendedKeys = [this.category.parentId];
                                    toast.success("保存成功", "bottom-end");

                                } else {
                                    this.$notify({
                                        title: '填写内容错误',
                                        message: resp.responseMsg,
                                        position: "top-right",
                                        type: 'warning'
                                    });
                                }
                            })
                    } else {
                        this.$notify({
                            title: '填写内容错误',
                            message: '请按照提示内容填写正确信息',
                            position: "top-right",
                            type: 'warning'
                        });
                        return false;
                    }
                });

            },
            //4.修改
            edit(category) {
                /*jquery继承对象： $.extend({新对象},旧对象)
                避免vue数据绑定漏洞，更改数据时，随之更改显示的data，但实际没有进行真实保存数据库
                 */
                this.dialogType = "更新分类";
                this.dialogTitle = "更新模块分类";
                this.dialogVisible = true;
                /*jquery继承对象： $.extend({新对象},旧对象) 如果我重新请求
               避免vue数据绑定漏洞，更改数据时，随之更改显示的data，但实际没有进行真实保存数据库
               this.module=$.extend({},module);
                */
                this.$axios.post(process.env.VUE_APP_SERVER + '/business/admin/category/info/' + category.uniId).then((response) => {
                    let resp = response.data;
                    if (resp.success) {
                        this.category = resp.responseData;
                    }
                })

            },
            //真删
            del(node, category) {
                /**
                 * 前端=》路径内携带参数使用url：'../'+{param}
                 * 后端=》requestMapping('../{param}')
                 *       void ...(@PathVariable String {param})
                 *
                 * ***引用toast的showConfirm全局方法，需要定义局部变量_this
                 * ***并且将存入的category转化为局部对象，才能获取到uniId
                 */
                let _this = this;
                let categoryParam = category;

                //el的确认框
                this.$confirm(`是否删除当前【${category.name}】分类？`, '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    _this.$axios.delete(process.env.VUE_APP_SERVER + '/business/admin/category/del/' + categoryParam.uniId)
                        .then((response) => {
                            let resp = response.data;
                            if (resp.success) {
                                this.$message({
                                    type: 'success',
                                    message: `【${category.name}】分类删除成功！`
                                });
                                _this.treeList();
                                //删除成功，展开之前的父节点
                                this.expendedKeys = [node.parent.data.uniId];
                            }
                        })

                })
                    .catch(() => {
                        this.$message({
                            type: 'info',
                            message: '已取消删除'
                        })
                    })
            },
            //更新显示show
            updateShow(row) {
                this.category = row;
                let responseMsg = '';
                //前端校验
                this.$axios.post(process.env.VUE_APP_SERVER + '/business/admin/category/save', this.category)
                    .then((response) => {
                        let resp = response.data;
                        responseMsg = response;

                        if (resp.success) {
                            this.list();
                            //引入全局变量toast提示
                            toast.success("保存成功", "bottom-end");
                        } else {
                            this.$notify({
                                title: '修改失败，请联系管理员!',
                                message: resp.responseMsg,
                                position: "top-right",
                                type: 'warning'
                            });
                        }
                    });
            },

            //拖拽方法
            allowDrop(draggingNode, dropNode, type) {
                //1.获取所有子节点，得到deepNode节点最大深度，弃用数据库中data.level，改用节点node的level
                this.countDeepLevel(draggingNode);
                /**
                 * 当前拖动节点draggingNode.data.level+draggingNode.parent.data.level>4 4级分类
                 3.当前最大深度按4级分类计算
                 总层级深度获取=层级插值+父节点层级!>4  deep.level-dragging.level+1+dragging.parent.level =this.maxLevel
                 当前层级深度获取=deep.level-dragging.level+1
                 计算绝对值：Math.abs，保证不出现负数
                 * */
                let currentDeep = Math.abs(this.maxLevel - draggingNode.level) + 1;
                //判断type：inner在节点内，关系是+ next+ prev-
                if (type === "inner") {
                    return (currentDeep + dropNode.level) <= 4;
                } else {
                    //父节点
                    return (currentDeep + dropNode.parent.level) <= 4;
                }
            },
            //计算deepNode总深度
            countDeepLevel(node) {
                //1.子节点不为空
                if (node.childNodes != null && node.childNodes.length > 0) {
                    //2.遍历子节点
                    for (let i = 0; i < node.childNodes.length; i++) {
                        //3.当前拖拽节点的--》子节点level>预设的maxLevel，则被拖动的子节点=maxLevel，替换
                        if (node.childNodes[i].level > this.maxLevel) {
                            this.maxLevel = node.childNodes[i].level;
                        }
                        //4.自调用，递归再次统一子节点的子节点是否存在
                        this.countDeepLevel(node.childNodes[i]);
                    }
                }
            },
            //拖拽成功回调
            handleDrop(draggingNode, dropNode, dropType, event) {
                //1.dragging当前节点的最新父节点-dropNode的最新父节点id
                let pId = [];
                let siblingNodes = null;
                /**如果为before/after情况，父节点为当前节点父节点的id*/
                if (dropType === "before" || dropType === "after") {
                    //判断如果为undefined，那么则为根节点，pId=0
                    pId = dropNode.parent.data.uniId === undefined ? '00000000' : dropNode.parent.data.uniId;
                    //before/after,需要遍历父节点的所有子节点
                    siblingNodes = dropNode.parent.childNodes;
                } else {
                    //inner，为当前节点id
                    pId = dropNode.data.uniId;
                    //为inner，则遍历当前节点的所有兄弟节点
                    siblingNodes = dropNode.childNodes;
                }
                //保存到全局pId
                this.pId.push(pId);

                //2.dropNode目标节点的最新sort，遍历
                for (let i = 0; i < siblingNodes.length; i++) {
                    /**当前拖拽节点，需要更新父id*/
                    //判断当前遍历对象的id为-当前draggingNode拖拽节点
                    if (siblingNodes[i].data.uniId === draggingNode.data.uniId) {
                        /**获取当前节点默认层级*/
                        let draggingLevel = draggingNode.level;
                        //当前draggingNode最新层级发生变化
                        if (siblingNodes[i].level !== draggingNode.level) {
                            /**获取最新层级*/
                            /*//如果为sibling兄弟before/after关系，则等同于目标节点的level
                            if(dropType==="before" || dropType==="after"){
                                //这里可以直接使用dropNode.data.level
                                draggingLevel=dropNode.level;
                            }else{
                                //如果为inner，那么为sibling兄弟.level+1
                                draggingLevel=dropNode.level+1;
                            }*/
                            /**当前节点层级发生变化，draggingNode子节点level也发生变化-->需要子节点递归子节点*/
                            draggingLevel = siblingNodes[i].level;
                            //更新子节点层级方法
                            this.updateDraggingNodeChildLevel(siblingNodes[i]);
                        }
                        this.updateNodes.push({
                            uniId: siblingNodes[i].data.uniId,
                            sort: i,
                            parentId: pId,
                            level: draggingLevel,
                        });

                    } else {
                        /**兄弟元素，只改顺序*/
                        //获取每个兄弟节点的uniId，当前需要更新的节点，返回id，解构为对细想
                        this.updateNodes.push({
                            uniId: siblingNodes[i].data.uniId,
                            sort: i,
                        });
                    }

                }
                //3.dropNode目标节点的最新层级
                //dropNode.parent.childNodes[0].data.level;
            },
            //更新拖拽后所有draggingNode子节点层级level
            updateDraggingNodeChildLevel(draggingNode) {
                if (draggingNode.childNodes.length > 0) {
                    for (let i = 0; i < draggingNode.childNodes.length; i++) {
                        //当前节点属性
                        let currentNode = draggingNode.childNodes[i].data;
                        //更新节点数据缓存：获取当前节点uniId、level
                        this.updateNodes.push({
                            uniId: currentNode.uniId,
                            level: draggingNode.childNodes[i].level
                        });
                        //递归更新子节点
                        this.updateDraggingNodeChildLevel(draggingNode.childNodes[i]);
                    }
                }

            },
            //拖拽批量保存
            batchDraggableSave() {
                //发送更新请求
                this.$axios.post(process.env.VUE_APP_SERVER + "/business/admin/category/update/level", this.updateNodes)
                    .then((response) => {
                        let resp = response.data;
                        if (resp.success) {
                            this.$message({
                                type: 'success',
                                message: `分类顺序更新成功！`
                            });
                            this.treeList();
                            //更新成功，展开之前的父节点数组
                            this.expendedKeys = this.pId;
                            //更新成功，初始化变量数据
                            this.updateNodes = [];
                            this.maxLevel = 0;
                            //this.pId='00000000';
                        }
                    });
            },
            //批量删除
            batchDel() {
                //let uniIds = [];
                let leafName=[];
                let checkedLeaf = this.$refs.categoryTree.getCheckedNodes();
                for (let i = 0; i < checkedLeaf.length; i++) {
                    /*注意axios只接收json和dataForm数据，如果是其他如string、[]list数据，将在前端被拦截
                    这里需要this.定义好的属性，axios自动转化为json
                    * */
                    this.uniIds.push(checkedLeaf[i].uniId);
                    leafName.push(checkedLeaf[i].name);
                }
                //let ids=JSON.stringify(uniIds);
                this.$confirm(`是否批量删除当前【${leafName}】分类？`, '提示', {
                    confirmButtonText: '确定',
                    cancelButtonText: '取消',
                    type: 'warning'
                }).then(() => {
                    //axios默认不允许传输数组
                    this.$axios.post(process.env.VUE_APP_SERVER + '/business/admin/category/delBatch',this.uniIds)
                        .then((response) => {
                            let resp = response.data;
                            if (resp.success) {
                                this.$message({
                                    type: 'success',
                                    message: `【${leafName}】分类删除成功！`
                                });
                                this.treeList();
                                //删除成功，展开之前的父节点
                                this.expendedKeys = this.pId;
                            }
                        })

                })
            },
        },


    }

</script>

<style scoped>
    /*顶部line样式*/
    .topLine {
        background-color: #2a92ee;
    }

    .lineIcon {
        font-size: 22px;
        color: #2a92ee;
        position: relative;
    }

    .lineTitle {
        position: relative;
        font-size: 16px;
        color: #2a92ee;
    }

    /*table样式*/
    .previous-row {
        background: #f0f9eb;
        margin-left: 2%;
        max-width: 96%;

    }

    .optionList {
        text-align: center;
    }

    /deep/ .el-table td, .el-table th {
        padding: 5px 0;
        text-align: center;
    }

    /deep/ .el-table .cell {
        padding-right: 2px;
        line-height: 20px;
        text-align: center;

    }

    /deep/ .el-table .cell, .el-table--border td:first-child .cell, .el-table--border th:first-child .cell {
        padding-left: 5px;
    }

    /*树型控件*/
    /deep/ .el-tree-node__content {
        height: 30px;
    }

    .categoryTree {
        position: absolute;
        margin-top: 80px;
        float: left;
        margin-left: 20px;
        width: 100%;
    }

    .nodeIcon {
        margin-right: 15px;
        color: #2a92ee;
        font-size: 15px;
    }

    .labelName {
        margin-right: 30px;
        font-size: 15px;
    }

    .opt-btn-add {
        font-size: 13px;
        padding: 3px 6px;
        margin-right: 4px;
    }

    .opt-btn-edit {
        font-size: 13px;
        padding: 3px 6px;
        margin-right: 4px;
    }

    .opt-btn-del {
        font-size: 13px;
        padding: 3px 6px;

    }

    /*拖拽开关*/
    .switchDrag {
        margin-top: 20px;
        margin-bottom: 30px;
        position: absolute;
        width: 100%;
    }

    .switchDragChoose {
        float: left;
        font-size: 25px;
        margin-top: 8px;
        margin-left: 30px;
    }

    .switchDragBtn {
        float: left;
        margin-left: 20px;
    }
</style>